/*
******************************************************************************
*
*   Copyright (C) 1999-2006, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*   file name:  udata.c
*   encoding:   US-ASCII
*   tab size:   8 (not used)
*   indentation:4
*
*   created on: 1999oct25
*   created by: Markus W. Scherer
*/

#include "inc/utypes.h"
#include "inc/udata.h"
#include "inc/putil.h"
#include "ucln_cmn.h"
#include "inc/ucnv.h"

#include "udatamem.h"
#include "umapfile.h"
#include "ucmndata.h"

#include <eltypes.h>

_ELASTOS_NAMESPACE_USING

/***********************************************************************
*
*   Notes on the organization of the ICU data implementation
*
*      All of the public API is defined in udata.h
*
*      The implementation is split into several files...
*
*         - udata.c  (this file) contains higher level code that knows about
*                     the search paths for locating data, caching opened data, etc.
*
*         - umapfile.c  contains the low level platform-specific code for actually loading
*                     (memory mapping, file reading, whatever) data into memory.
*
*         - ucmndata.c  deals with the tables of contents of ICU data items within
*                     an ICU common format data file.  The implementation includes
*                     an abstract interface and support for multiple TOC formats.
*                     All knowledge of any specific TOC format is encapsulated here.
*
*         - udatamem.c has code for managing UDataMemory structs.  These are little
*                     descriptor objects for blocks of memory holding ICU data of
*                     various types.
*/


#include <_pubcrt.h>
#include <stdlib.h>

#ifdef  __cplusplus
extern "C" {
#endif

extern PVoid _Impl_Get_Encoding_Data();

/***********************************************************************
*
*    static (Global) data
*
************************************************************************/
static UDataMemory g_CommonICUData;

static UDataMemory *s_pCommonICUData = NULL;    /* Pointer to the common ICU data.           */
                                              /*   May be updated once, if we started with */
                                              /*   a stub or subset library.               */

/* ==================================================================================*/


/*----------------------------------------------------------------------*
 *                                                                      *
 *  Add a static reference to the common data  library                  *
 *   Unless overridden by an explicit udata_setCommonData, this will be *
 *      our common data.                                                *
 *                                                                      *
 *----------------------------------------------------------------------*/
extern  const DataHeader U_DATA_API U_ICUDATA_ENTRY_POINT;


/*----------------------------------------------------------------------------*
 *                                                                            *
 *  checkDataItem     Given a freshly located/loaded data item, either        *
 *                    an entry in a common file or a separately loaded file,  *
 *                    sanity check its header, and see if the data is         *
 *                    acceptable to the app.                                  *
 *                    If the data is good, create and return a UDataMemory    *
 *                    object that can be returned to the application.         *
 *                    Return NULL on any sort of failure.                     *
 *                                                                            *
 *----------------------------------------------------------------------------*/
static UDataMemory *
checkDataItem
(
 const DataHeader         *pHeader,         /* The data item to be checked.                */
 UDataMemoryIsAcceptable  *isAcceptable,    /* App's call-back function                    */
 void                     *context,         /*   pass-thru param for above.                */
 const char               *type,            /*   pass-thru param for above.                */
 const char               *name,            /*   pass-thru param for above.                */
 UErrorCode               *nonFatalErr,     /* Error code if this data was not acceptable  */
                                            /*   but openChoice should continue with       */
                                            /*   trying to get data from fallback path.    */
 UErrorCode               *fatalErr         /* Bad error, caller should return immediately */
 )
{
    UDataMemory  *rDataMem = NULL;          /* the new UDataMemory, to be returned.        */

    if (U_FAILURE(*fatalErr)) {
        return NULL;
    }

    if(pHeader->dataHeader.magic1==0xda &&
        pHeader->dataHeader.magic2==0x27 &&
        (isAcceptable==NULL || isAcceptable(context, type, name, &pHeader->info))
    ) {
        rDataMem=UDataMemory_createNewInstance(fatalErr);
        if (U_FAILURE(*fatalErr)) {
            return NULL;
        }
        rDataMem->pHeader = pHeader;
    } else {
        /* the data is not acceptable, look further */
        /* If we eventually find something good, this errorcode will be */
        /*    cleared out.                                              */
        *nonFatalErr=U_INVALID_FORMAT_ERROR;
    }
    return rDataMem;
}

/**
 * @return 0 if not loaded, 1 if loaded or err
 */
static UDataMemory *doLoadFromCommonData(UBool isICUData, const char *tocEntryName,
             const char *type, const char *name,
             UDataMemoryIsAcceptable *isAcceptable, void *context,
             UErrorCode *subErrorCode,
             UErrorCode *pErrorCode)
{
    UDataMemory *retVal = NULL;
    UDataMemory        *pCommonData;
    const DataHeader   *pHeader;
    int                length;

    if (s_pCommonICUData == NULL) {
        UDataMemory_init(&g_CommonICUData);
        g_CommonICUData.pHeader = (DataHeader *)_Impl_Get_Encoding_Data();
        udata_checkCommonData(&g_CommonICUData, pErrorCode);
        if (U_FAILURE(*pErrorCode)) {
            return  NULL;
        }
        s_pCommonICUData = &g_CommonICUData;
    }

    pCommonData = s_pCommonICUData;

    /* look up the data piece in the common data */
    pHeader = pCommonData->vFuncs->Lookup(pCommonData, tocEntryName, &length, subErrorCode);
    if(pHeader == NULL) {
        return NULL;
    }

    retVal = checkDataItem(pHeader, isAcceptable, context, type, name, subErrorCode, pErrorCode);

    if (retVal != NULL) retVal->length = length;

    return retVal;
}

/**
 * @return 0 if not loaded, 1 if loaded or err
 */
static UDataMemory *doLoadFromIndividualFiles(const char *pkgName,
        const char *dataPath, const char *tocEntryPathSuffix,
            /* following arguments are the same as doOpenChoice itself */
            const char *path, const char *type, const char *name,
             UDataMemoryIsAcceptable *isAcceptable, void *context,
             UErrorCode *subErrorCode,
             UErrorCode *pErrorCode)
{
    UDataMemory *retVal = NULL;
	char         pathBuffer[_MAX_PATH];
    UDataMemory         dataMemory;
    UDataMemory *pEntryData;

//	if (dataPath == NULL)
//		dataPath = u_getDataDirectory();
	if (strlen(dataPath) + strlen(pkgName) + strlen(tocEntryPathSuffix) + 1 > _MAX_PATH)
		goto commonReturn;

	strcpy(pathBuffer, dataPath);
	strcat(pathBuffer, U_FILE_SEP_STRING);
	strcat(pathBuffer, pkgName);
	strcat(pathBuffer, tocEntryPathSuffix);

    if(uprv_mapFile(&dataMemory, pathBuffer)) {
        pEntryData = checkDataItem(dataMemory.pHeader, isAcceptable, context, type, name, subErrorCode, pErrorCode);
        if (pEntryData != NULL) {
           	/* Data is good.
           	*  Hand off ownership of the backing memory to the user's UDataMemory.
           	*  and return it.   */
           	pEntryData->mapAddr = dataMemory.mapAddr;
           	pEntryData->map     = dataMemory.map;

           	retVal = pEntryData;
          	goto commonReturn;
       }

       /* the data is not acceptable, or some error occured.  Either way, unmap the memory */
       udata_close(&dataMemory);

       /* If we had a nasty error, bail out completely.  */
       if (U_FAILURE(*pErrorCode)) {
           	retVal = NULL;
           	goto commonReturn;
       }

       /* Otherwise remember that we found data but didn't like it for some reason  */
       *subErrorCode=U_INVALID_FORMAT_ERROR;
    }

commonReturn:
    return retVal;
}

/*
 *  A note on the ownership of Mapped Memory
 *
 *  For common format files, ownership resides with the UDataMemory object
 *    that lives in the cache of opened common data.  These UDataMemorys are private
 *    to the udata implementation, and are never seen directly by users.
 *
 *    The UDataMemory objects returned to users will have the address of some desired
 *    data within the mapped region, but they wont have the mapping info itself, and thus
 *    won't cause anything to be removed from memory when they are closed.
 *
 *  For individual data files, the UDataMemory returned to the user holds the
 *  information necessary to unmap the data on close.  If the user independently
 *  opens the same data file twice, two completely independent mappings will be made.
 *  (There is no cache of opened data items from individual files, only a cache of
 *   opened Common Data files, that is, files containing a collection of data items.)
 *
 *  For common data passed in from the user via udata_setAppData() or
 *  udata_setCommonData(), ownership remains with the user.
 *
 *  UDataMemory objects themselves, as opposed to the memory they describe,
 *  can be anywhere - heap, stack/local or global.
 *  They have a flag to indicate when they're heap allocated and thus
 *  must be deleted when closed.
 */

static UDataMemory *
doOpenChoice(const char *path, const char *type, const char *name,
             UDataMemoryIsAcceptable *isAcceptable, void *context,
             UErrorCode *pErrorCode)
{
    UDataMemory         *retVal = NULL;

    char    asbEntryName[100]; /* entry name in tree format. ex:  'icudt28b/coll/ar.res' */
    char    asbEntryPath[100]; /* entry name in path format. ex:  'icudt28b\\coll\\ar.res' */
    char    pkgName[20];
    const char          *dataPath;
    UErrorCode          subErrorCode=U_ZERO_ERROR;
    UBool               isICUData = FALSE;


    /* Is this path ICU data? */
    if(path == NULL ||
       !strcmp(path, U_ICUDATA_ALIAS) ||  /* "ICUDATA" */
       !strncmp(path, U_ICUDATA_NAME U_TREE_SEPARATOR_STRING, /* "icudt26e-" */
                     strlen(U_ICUDATA_NAME U_TREE_SEPARATOR_STRING)) ||
       !strncmp(path, U_ICUDATA_ALIAS U_TREE_SEPARATOR_STRING, /* "ICUDATA-" */
                     strlen(U_ICUDATA_ALIAS U_TREE_SEPARATOR_STRING))) {
      isICUData = TRUE;
    }

	strcpy(asbEntryName, U_ICUDATA_NAME);
	strcat(asbEntryName, U_TREE_ENTRY_SEP_STRING);
	strcat(asbEntryName, name);
	if (type!=NULL && *type!=0) {
		strcat(asbEntryName, ".");
		strcat(asbEntryName, type);
	}

    /* packages are first, here only default converter in packages */
    retVal = doLoadFromCommonData(isICUData, asbEntryName,
                    type, name, isAcceptable, context, &subErrorCode, pErrorCode);
    if((retVal != NULL) || U_FAILURE(*pErrorCode)) {
    	goto commonReturn;
    }

	strcpy(asbEntryPath, U_FILE_SEP_STRING);
	strcat(asbEntryPath, name);
	if (type!=NULL && *type!=0) {
		strcat(asbEntryPath, ".");
		strcat(asbEntryPath, type);
	}
    if(path == NULL) {
        path = COMMON_DATA_NAME;
        strcpy(pkgName, U_ICUDATA_NAME);
    }
    dataPath = u_getDataDirectory();

	/* packages do not have, files are next */
    retVal = doLoadFromIndividualFiles(pkgName, dataPath, asbEntryPath,
                         path, type, name, isAcceptable, context, &subErrorCode, pErrorCode);

	if((retVal != NULL) || U_FAILURE(*pErrorCode)) {
    	goto commonReturn;
    }

	/* data not found */
    if(U_SUCCESS(*pErrorCode)) {
        if(U_SUCCESS(subErrorCode)) {
            /* file not found */
            *pErrorCode=U_FILE_ACCESS_ERROR;
        } else {
            /* entry point not found or rejected */
            *pErrorCode=subErrorCode;
        }
    }

commonReturn:
    return retVal;
}


U_CAPI UDataMemory * U_EXPORT2
udata_openChoice(const char *path, const char *type, const char *name,
                 UDataMemoryIsAcceptable *isAcceptable, void *context,
                 UErrorCode *pErrorCode) {
#ifdef UDATA_DEBUG
  fprintf(stderr, "udata_openChoice(): Opening: %s : %s . %s\n", (path?path:"NULL"), name, type);
#endif

    if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) {
        return NULL;
    } else if(name==NULL || *name==0 || isAcceptable==NULL) {
        *pErrorCode=U_ILLEGAL_ARGUMENT_ERROR;
        return NULL;
    } else {
        return doOpenChoice(path, type, name, isAcceptable, context, pErrorCode);
    }
}



U_CAPI void U_EXPORT2
udata_getInfo(UDataMemory *pData, UDataInfo *pInfo) {
    if(pInfo!=NULL) {
        if(pData!=NULL && pData->pHeader!=NULL) {
            const UDataInfo *info=&pData->pHeader->info;
            uint16_t dataInfoSize=udata_getInfoSize(info);
            if(pInfo->size>dataInfoSize) {
                pInfo->size=dataInfoSize;
            }
            memcpy((uint16_t *)pInfo+1, (const uint16_t *)info+1, pInfo->size-2);
            if(info->isBigEndian!=U_IS_BIG_ENDIAN) {
                /* opposite endianness */
                uint16_t x=info->reservedWord;
                pInfo->reservedWord=(uint16_t)((x<<8)|(x>>8));
            }
        } else {
            pInfo->size=0;
        }
    }
}

#ifdef  __cplusplus
}
#endif
